﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {
    /// <summary>
    /// 見え方を規定するアピアランス
    /// </summary>
    /// <remarks>
    /// アピアランスは物体の表示方法を制御するアピアランス要素を保存するコンテナクラスです。
    /// 見え方に関係する全てのオブジェクトは<dic>Appearance</dic>の下に保管します。
    /// 要素は具体的に言うと描画に使用されるシェーダー<see cref="ShaderProgram"/>や<see cref="UniformArrays"/>、
    /// マテリアルパラメーターを保存する<see cref="Material"/>とテクスチャー<see cref="Sampler"/>、
    /// シェーダーから制御できないパラメーターである<see cref="PolygonMode"/>, <see cref="ColorBlendFunction"/>, <see cref="PointSpriteMode"/>
    /// があります。
    /// </remarks>
    public class Appearance : Object {

        #region Field
        ShaderProgram shader;
        Material material;
        List<UniformArray> uniformArrays;
        List<Sampler> samplers;
        PolygonMode polygonMode;
        CompositeMode compositeMode;
        PointSpriteMode pointSpriteMode;
        #endregion

        #region Constructor
        /// <summary>
        /// アピアランスオブジェクトを作成するコンストラクター
        /// </summary>
        public Appearance () {
            this.shader = null;
            this.material = null;
            this.uniformArrays = new List<UniformArray> ();
            this.samplers = new List<Sampler>();
            this.polygonMode = null;
            this.compositeMode = null;
            this.pointSpriteMode = null;
        }
        #endregion

        #region Property
        /// <summary>
        /// このアピアランスで使用されるシェーダープログラム
        /// </summary>
        /// <value>シェーダープログラム</value>
        public ShaderProgram ShaderProgram {
            get { return shader; }
        }

        /// <summary>
        /// このアピアランスで使用されるマテリアル
        /// </summary>
        /// <value>マテリアル</value>
        public Material Material {
            get { return material; }
        }

        /// <summary>
        /// このアピアランスで使用される<see cref="UniformArray"/>の個数
        /// </summary>
        public int UniformArrayCount {
            get { return uniformArrays.Count; }
        }

        /// <summary>
        /// このアピアランスで使用される<see cref="UniformArray"/>を列挙する列挙子
        /// </summary>
        public IEnumerable<UniformArray> UniformArrays {
            get { return uniformArrays.Where(x => x != null); }
        }

        /// <summary>
        /// このアピアランスで使用される<see cref="PolygonMode"/>
        /// </summary>
        public PolygonMode PolygonMode {
            get { return polygonMode; }
        }

        /// <summary>
        /// このアピアランスで使用される<see cref="CompositeMode"/>
        /// </summary>
        public CompositeMode CompositeMode {
            get { return compositeMode; }
        }

        /// <summary>
        /// このアピアランスで使用される<see cref="PointSpriteMode"/>
        /// </summary>
        public PointSpriteMode PointSpriteMode {
            get { return pointSpriteMode; }
        }

        /// <summary>
        /// このアピアランスに現在セットされているサンプラーの個数
        /// </summary>
        public int SamplerCount {
            get { return samplers.Count(); }
        }

        /// <summary>
        /// このアピアランスで使用されるサンプラーを列挙する列挙子
        /// </summary>
        public IEnumerable<Sampler> Samplers {
            get { return samplers.Where (x => x != null); }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.Add (shader);
                objs.Add (material);
                objs.Add (polygonMode);
                objs.Add (compositeMode);
                objs.Add (pointSpriteMode);
                objs.AddRange (samplers);
                objs.AddRange (uniformArrays);
                objs.RemoveAll (x=>x==null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// シェーダープログラムをセットします
        /// </summary>
        /// <remarks>
        /// この物体を描画するのに使用するシェーダープログラムをセットします。
        /// nullを指定するとセットされていたシェーダーが取り除かれます。
        /// </remarks>
        /// <param name="shader">シェーダープログラム</param>
        public void SetShaderProgram (ShaderProgram shader) {
            this.shader = shader;
        }

        /// <summary>
        /// マテリアルをセットします
        /// </summary>
        /// <remarks>
        /// この物体を描画するのに使用するマテリアルをセットします。
        /// マテリアルパラメーターは<see cref="UniformArrays"/>にセットしてエクスポートするとシェーダから使えるようになります。
        /// ここにセットしただけではまだ使えません。ここは単なるプレイスホルダーです。
        /// nullを指定するとセットされていたマテリアルが取り除かれます。
        /// <see cref="UniformArray"/>に
        /// </remarks>
        /// <param name="material">マテリアル</param>
        public void SetMaterial (Material material) {
            this.material = material;
        }

        /// <summary>
        /// <see cref="UniformArray"/>を追加します
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用されるシェーダーに指定のUniform変数をエクスポートします。
        /// Uniform変数の集合を<see cref="UniformArray"/>といい、この<see cref="UniformArray"/>単位で
        /// Uniform変数を追加・削除します。<see cref="UniformArray"/>は幾つでも<dic>Appearance</dic>に追加することが可能です。
        /// 既にセットされていた<see cref="UniformArray"/>オブジェクトを再度セットした場合何もしません。
        /// </remarks>
        /// <param name="uniformArray">Uniform変数の集合</param>
        public void AddUniformArray (UniformArray uniformArray) {
            if (uniformArray == null) {
                throw new ArgumentNullException ("UniformArray is null");
            }
            if (uniformArrays.Contains (uniformArray)) {
                return;
            }

            this.uniformArrays.Add (uniformArray);
        }

        /// <summary>
        /// <see cref="UniformArray"/>を削除します
        /// </summary>
        /// <remarks>
        /// 登録済みの<see cref="UniformArray"/>を削除します。
        /// nullもしくはそれが登録されていなかった場合は何もしません。
        /// </remarks>
        /// <param name="uniformArray">UniformArray</param>
        /// <returns>削除したらtrue, そうでなければfalse</returns>
        public bool RemoveUniformArray (UniformArray uniformArray) {
            return this.uniformArrays.Remove (uniformArray);
        }

        /// <summary>
        /// 指定のインデックスの<see cref="UniformArray"/>オブジェクトを取得します
        /// </summary>
        /// <remarks>
        /// 設定済みの<see cref="UniformArray"/>を取得します
        /// </remarks>
        /// <param name="index">インデックス番号</param>
        /// <returns><see cref="UniformArray"/>オブジェクト</returns>
        public UniformArray GetUniformArray (int index) {
            if (index < 0 || index >= UniformArrayCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return uniformArrays[index];
        }

        /// <summary>
        /// <see cref="PolygonMode"/>をセットします
        /// </summary>
        /// <remarks>
        /// <see cref="PolygonMode"/>はカリングルールと面の向きを規定します。
        /// nullを指定すると設定済みのものを削除します。
        /// </remarks>
        /// <param name="pmode">セットする<see cref="PolygonMode"/>オブジェクト</param>
        public void SetPolygonMode (PolygonMode pmode) {
            this.polygonMode = pmode;
        }

        /// <summary>
        /// <see cref="CompositeMode"/>をセットします
        /// </summary>
        /// <remarks>
        /// <see cref="CompositeMode"/>はフラグメントをフレームバッファーに合成する方法を規定します。
        /// nullを指定すると設定済みのものを削除します。
        /// </remarks>
        /// <param name="cmode">セットする<see cref="CompositeMode"/>オブジェクト</param>
        public void SetComositeMode (CompositeMode cmode) {
            this.compositeMode = cmode;
        }

        /// <summary>
        /// <see cref="PointSpriteMode"/>をセットします
        /// </summary>
        /// <remarks>
        /// <see cref="PointSpriteMode"/>はポイントスプライトの描画方法を規定します。
        /// nullを指定すると設定済みのものを削除します。
        /// このアピアランス要素はポイントスプライトを描画するときのみ使用されます。
        /// その他のプリミティブを描画するときは無視されます。
        /// </remarks>
        /// <param name="psmode">セットする<see cref="PointSpriteMode"/>オブジェクト</param>
        public void SetPointSpriteMode (PointSpriteMode psmode) {
            this.pointSpriteMode = psmode;
        }

        /// <summary>
        /// テクスチャーをセットします
        /// </summary>
        /// <remarks>
        /// 指定の番号のテクスチャーユニットにテクスチャーサンプラーをセットします。
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        public void AddSampler (Sampler sampler) {
            if (samplers.Contains (sampler)) {
                return;
            }

            this.samplers.Add (sampler);
        }

        /// <summary>
        /// テクスチャーを削除します
        /// </summary>
        /// <remarks>
        /// このアピアランスから指定のテクスチャーを削除します。
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        public bool RemoveSampler (Sampler sampler) {
            return samplers.RemoveAll (x => x == sampler) > 0 ? true : false;
        }

        /// <summary>
        /// サンプラーを取得します
        /// </summary>
        /// <remarks>
        /// サンプラーを取得します。
        /// </remarks>
        /// <param name="index">テクスチャーユニット番号</param>
        /// <returns>テクスチャーオブジェクト</returns>
        public Sampler GetSampler (int index) {
            if (index < 0 || index >= SamplerCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return samplers[index];
        }
        
        /// <summary>
        /// サンプラー番号を取得します
        /// </summary>
        /// <remarks>
        /// サンプラー番号はテクスチャーユニット番号に等しいです。
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        /// <returns>インデックス</returns>
        public int GetSamplerIndex (Sampler sampler) {
            if (sampler == null) {
                throw new ArgumentNullException ("Sampler is null");
            }
            if (!samplers.Contains (sampler)) {
                throw new ArgumentException ("Sampler was not found");
            }

            return samplers.IndexOf (sampler);
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        public override string ToString () {
            return string.Format ("Appearance: SP={0}", ShaderProgram);
        }

        #endregion


    }
}
